package com.diy.sigmund.diycommon.spring.aspect;

import com.diy.sigmund.diycommon.entity.ResultMsg;
import com.diy.sigmund.diycommon.entity.base.exception.DiyRuntimeException;
import com.diy.sigmund.diycommon.entity.constant.LogConstant;
import com.diy.sigmund.diycommon.entity.enumerate.ResponseEnum;
import com.diy.sigmund.diycommon.entity.enumerate.VerifyParamPolicyEnum;
import com.diy.sigmund.diycommon.spring.anno.VerifyParam;
import com.diy.sigmund.diycommon.spring.anno.VerifyParams;
import com.diy.sigmund.diycommon.util.VerifyParamUtil;
import com.diy.sigmund.diycommon.util.time.DateUtil;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * 入参校验切面
 *
 * @author ylm-sigmund
 * @since 2021/5/24 22:11
 */
@Order(6)
@Component
@Aspect
public class VerifyParamAspect {

    private static final Logger log = LoggerFactory.getLogger(VerifyParamAspect.class);

    @Pointcut("@annotation(com.diy.sigmund.diycommon.spring.anno.VerifyParams)")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info(LogConstant.SPRING_LOG, "入参校验切面处理 开始");
        final MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 方法
        Method method = signature.getMethod();
        // 获取方法上的注解
        final VerifyParams verifyParams = method.getAnnotation(VerifyParams.class);
        Object msg = handle(pjp, method, verifyParams);
        if (Objects.nonNull(msg)) {
            return msg;
        }
        log.info(LogConstant.SPRING_LOG, "入参校验切面处理 结束");
        return pjp.proceed();
    }

    /**
     * 处理VerifyParams注解
     *
     * @param pjp          pjp
     * @param method       method
     * @param verifyParams verifyParams
     * @return 校验信息
     */
    private Object handle(ProceedingJoinPoint pjp, Method method, VerifyParams verifyParams) {
        if (Objects.isNull(verifyParams)) {
            return null;
        }
        final VerifyParam[] params = verifyParams.value();
        Object requestBodyValue = getObject(pjp, method);
        for (VerifyParam param : params) {
            Object msg = handleVerifyParam(param, requestBodyValue);
            if (Objects.nonNull(msg)) {
                return msg;
            }
        }
        return null;
    }

    /**
     * 处理VerifyParam注解
     *
     * @param verifyParam      verifyParam
     * @param requestBodyValue requestBodyValue
     * @return 校验信息
     */

    private Object handleVerifyParam(VerifyParam verifyParam, Object requestBodyValue) {
        Class clazz = requestBodyValue.getClass();

        final String fieldName = verifyParam.fieldName();
        final VerifyParamPolicyEnum verifyPolicy = verifyParam.verifyPolicy();
        final String verifyValue = verifyParam.verifyValue();

        // 获取object的属性描述
        try {
            PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz,
                    Object.class).getPropertyDescriptors();
            // 这里就是所有的属性了
            for (PropertyDescriptor pd : pds) {
                // 属性名
                String name = pd.getName();
                if (Objects.equals(fieldName, name)) {
                    log.info("开始参数校验字段:{}", name);
                    // get方法
                    Method readMethod = pd.getReadMethod();
                    // 调用get方法等同于获得属性值
                    Object fieldValue = readMethod.invoke(requestBodyValue);
                    final Boolean result = verifyPolicy.getVerifyParamPolicy().apply(fieldValue, verifyValue);
                    if (!result) {
                        final String msg = getMsg(verifyParam, verifyPolicy, fieldValue, verifyValue);
                        return ResultMsg.fail(verifyParam.code(), msg);
                    }
                    log.info("参数校验通过:{}", name);
                }
            }
        } catch (Exception e) {
            log.error("handleVerifyParam error", e);
            throw new DiyRuntimeException(ResponseEnum.INTERNAL_SERVER_ERROR.getCode(), "handleVerifyParam error");
        }
        return null;
    }

    private String getMsg(VerifyParam verifyParam, VerifyParamPolicyEnum verifyPolicy, Object fieldValue,
            String verifyValue) {
        String msg = verifyParam.msg();
        final String fieldName = verifyParam.fieldName();
        if (StringUtils.isBlank(msg)) {
            final int replacementQuantity = verifyPolicy.getReplacementQuantity();
            final String policyMsg = verifyPolicy.getMsg();
            if (Objects.equals(replacementQuantity, 1)) {
                msg = String.format(policyMsg, fieldName);
            } else if (Objects.equals(replacementQuantity, 2)) {
                msg = String.format(policyMsg, fieldName, verifyValue);
            } else if (Objects.equals(replacementQuantity, 3)) {
                final String[] splitStr = VerifyParamUtil.getSplitStr(verifyValue);
                if (fieldValue instanceof Date) {
                    fieldValue = DateUtil
                            .parseDateToStr((Date) fieldValue, DateUtil.DATE_TIME_FORMAT_YYYY_MM_DD_HH_MI_SS);
                }
                msg = String.format(policyMsg, fieldName, splitStr[0], splitStr[1]);
            } else {
                throw new DiyRuntimeException(ResponseEnum.INTERNAL_SERVER_ERROR.getCode(), "getMsg error");
            }
        }
        return msg;
    }

    /**
     * 获取RequestBody注解对应的入参
     *
     * @param pjp    pjp
     * @param method method
     * @return requestBodyValue
     */
    private Object getObject(ProceedingJoinPoint pjp, Method method) {
        // 获取所有参数上的注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        // 拿到所有的参数
        final Object[] args = pjp.getArgs();
        // 获取所有参数注解返回的是一个二维数组Annotation[][],
        // 每个参数上可能有多个注解,是一个一维数组,多个参数又是一维数组,就组成了二维数组,
        // 所有我们在遍历的时候,第一次遍历拿到的数组下标就是方法参数的下标,
        // 根据指定的下标即可拿到对象的值
        Object requestBodyValue = null;
        for (Annotation[] parameterAnnotation : parameterAnnotations) {
            int paramIndex = ArrayUtils.indexOf(parameterAnnotations, parameterAnnotation);
            for (Annotation annotation : parameterAnnotation) {
                if (annotation instanceof RequestBody) {
                    requestBodyValue = args[paramIndex];
                    break;
                }
            }
        }
        if (Objects.isNull(requestBodyValue)) {
            // 入参为空时返回
            throw new DiyRuntimeException(ResponseEnum.REQUEST_BODY_IS_MISSING);
        }
        return requestBodyValue;
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        log.info(LogConstant.SPRING_LOG, "入参校验切面处理 结束");
    }
}
